Skip to content

feat(router): regex selectors for subgraph header rules (subgraph_patterns)#2868

Open
mwisner wants to merge 1 commit into
wundergraph:mainfrom
mwisner:feat/router-header-subgraph-patterns
Open

feat(router): regex selectors for subgraph header rules (subgraph_patterns)#2868
mwisner wants to merge 1 commit into
wundergraph:mainfrom
mwisner:feat/router-header-subgraph-patterns

Conversation

@mwisner
Copy link
Copy Markdown

@mwisner mwisner commented May 14, 2026

AI disclosure: This PR was drafted with assistance from an AI coding agent (opencode + Claude). All code, tests, and docs were reviewed by a human before submission. Flagging this so reviewers can apply appropriate scrutiny.

Alternative proposal — see also #2869: This PR and #2869 solve the same problem with different API surface area. #2869 introduces richer headers.groups (named bundles with list, regex, or hybrid selectors) that subsume regex-only patterns. Either can land; they're not meant to ship together. This PR is the smaller, more focused primitive.

Why

Today, subgraph-specific header rules in the router are matched by exact subgraph name. Feature subgraphs are composed under their own name (e.g. products-feature-v2) rather than the base subgraph's name (products), so per-subgraph rules defined for the base subgraph do not apply to its feature variants. Only headers.all rules carry over automatically.

There are two existing options for handling header propagation to feature subgraphs:

  1. Use headers.all. This applies the rule to every subgraph regardless of name, so feature subgraphs are covered. The trade-off is that the rule is also applied to every other subgraph in the graph — including subgraphs that have no need for the header. For users who treat subgraph header lists as an explicit allow-list (to avoid leaking headers to subgraphs that shouldn't see them, or to keep request-deduplication keys tight), this is broader than they want.

  2. Add a separate headers.subgraphs.<feature-name> block for each feature subgraph. This works and is precise, but the rule has to be authored statically against each feature subgraph name. For workflows where feature subgraphs are short-lived or numerous (for example, per-branch or per-PR feature subgraphs), this means router config edits and a router redeploy each time a feature subgraph is created or renamed. Without hot reload, that affects all GraphQL traffic for what should be a localized change.

This PR adds an opt-in third option: regex selectors that target a group of subgraphs by name. A single rule can cover a base subgraph and its feature variants without listing each one.

What

A new ordered list headers.subgraph_patterns whose matching field is a Go regex evaluated against the subgraph name.

Schema example

Before — a rule under subgraphs.products only matches the literal name products:

headers:
  all:
    request:
      - op: propagate
        named: X-Request-Id

  subgraphs:
    products:
      request:
        - op: propagate
          named: X-Products-Auth   # never reaches products-feature-pr-123

After — a single pattern entry covers the base subgraph and every feature variant:

headers:
  all:
    request:
      - op: propagate
        named: X-Request-Id

  subgraphs:
    products:                       # exact-name rules still work, unchanged
      request:
        - op: propagate
          named: X-Products-Internal

  subgraph_patterns:
    - matching: "^products(-feature-.+)?$"
      request:
        - op: propagate
          named: X-Products-Auth    # applies to products and products-feature-*
      response:
        - op: propagate
          named: X-Products-Trace
          algorithm: last_write

negate_match: true is also supported, mirroring how it already works on header-name regexes:

  subgraph_patterns:
    - matching: "^internal-.+$"
      negate_match: true
      request:
        - op: propagate
          named: X-Public

Rule evaluation order

For each subgraph the router builds a header set in this order:

  1. headers.all rules
  2. headers.subgraph_patterns entries whose regex matches the subgraph name (in config order)
  3. headers.subgraphs.<name> rules for the exact subgraph name

Exact-name rules apply last so they can still override a broader pattern rule (e.g. via op: set). This matches how the existing two layers compose today.

Backward compatibility

No breaking changes.

  • subgraph_patterns is a new optional field. Configs without it behave identically to before.
  • headers.subgraphs.<name> continues to match by exact name with no semantic change.
  • No proto / router execution config changes — entirely router-local.
  • A fast-path skips all pattern logic when subgraph_patterns is empty (hasAnyPatternRequestRules == false), so existing users see zero added work per request.

Performance

The hot path is BuildRequestHeaderForSubgraph, called per data source in the execution plan via SubgraphHeadersBuilder.

Configuration Cost added by this PR
No subgraph_patterns None — guarded by hasAnyPatternRequestRules short-circuit
n patterns configured, plan touches k subgraphs k × n regex.MatchString calls per built header set

Concrete properties:

  • Regexes compiled once at startup in NewHeaderPropagation, stored in subgraphPatternRegex []*regexp.Regexp. Invalid regexes fail router init rather than at request time.
  • No ReDoS risk — Go's regexp uses RE2, which is linear-time and rejects backtracking constructs.
  • No per-match allocations for typical subgraph names.
  • For realistic configs (n ≤ 5, k ≤ 20, names ~30 chars) this adds <10 µs per request, well below surrounding planning cost.
  • One non-critical optimization left out for reviewer discretion: the package-level SubgraphRules helper (called during engine factory init, not on the request path) re-compiles pattern regexes per call. Easy to share the pre-compiled regexes if reviewers prefer; left as-is to keep the diff focused.

Implementation summary

File Change
router/pkg/config/config.go New SubgraphPatternHeaderRule type and HeaderRules.SubgraphPatterns field
router/core/header_rule_engine.go Compile selectors at startup; apply patterns in request and response paths; extend hasRequestRulesForSubgraph and SubgraphRules
router/pkg/config/config.schema.json Schema entry under headers.subgraph_patterns
router/pkg/config/testdata/*.json Updated golden fixtures for the new field
router/core/header_rule_engine_test.go Response-rule pattern test
router/core/header_rule_engine_buildheader_test.go Request-rule pattern tests: matching, negate_match, invalid regex, missing matching, all → patterns → exact ordering, pattern-only fast-path, SubgraphRules helper coverage
docs-website/router/proxy-capabilities/subgraph-request-header-operations.mdx Example, evaluation order, and pattern selector reference
docs-website/router/proxy-capabilities/subgraph-response-header-operations.mdx Example and explanation paragraph

Validation

Locally on darwin/arm64 with Go 1.26.3:

go build ./...                          # clean
go vet ./core/... ./pkg/config/...      # clean
go test ./core/                         # PASS (incl. new TestBuildRequestHeaderForSubgraph_PatternRules,
                                        #               TestSubgraphRules_PatternsIncluded,
                                        #               TestApplyResponseHeaderRules_PatternRules)
go test ./pkg/config/                   # PASS (golden fixtures updated)

Notes for reviewers

  • Happy to drop the PR or rework the API shape if you'd prefer a different surface (e.g. nesting under subgraphs: with a sigil instead of a separate top-level list). Wanted to keep it explicit and additive to avoid colliding with existing exact-name rules.
  • Open question: should SubgraphRules share the pre-compiled regex slice with HeaderPropagation instead of recompiling? Easy follow-up; called out above.
  • Alternative I considered and rejected: automatic inheritance from base subgraph to feature subgraph. Cleaner UX, but proto/wg/cosmo/node/v1/node.proto only carries Subgraph{id, name, routing_url}, so feature-subgraph → base-subgraph linkage would need execution-config changes and a clearer compatibility story. Regex selectors are router-local and additive.

Adds an ordered `subgraph_patterns` list under `headers` whose `matching`
field is a Go regex applied to the subgraph name. Patterns are evaluated
after `headers.all` and before exact `headers.subgraphs.<name>` rules,
letting a single rule cover a base subgraph and its feature subgraphs
without duplicating per-subgraph blocks for every preview deployment.

Pattern regexes are compiled once at startup; invalid regexes fail
`NewHeaderPropagation`. Existing configs without `subgraph_patterns` hit
a fast-path and pay no additional cost.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 14, 2026

Walkthrough

This PR extends the router's header propagation engine to support pattern-based rule matching on subgraph names via Go regexes. Rules are evaluated in order: global rules, then matching pattern rules (with optional negation), then exact per-subgraph rules. Both request and response header paths are updated.

Changes

Subgraph Pattern Header Rules

Layer / File(s) Summary
Configuration types and schema
router/pkg/config/config.go, router/pkg/config/config.schema.json, router/pkg/config/testdata/config_*.json
Introduces SubgraphPatternHeaderRule type with Matching regex, optional NegateMatch, and Request/Response rule lists. HeaderRules struct gains SubgraphPatterns field. JSON schema and test configs updated to include the new configuration option.
Header rule engine pattern matching
router/core/header_rule_engine.go
Extends HeaderPropagation with compiled pattern regexes and metadata. NewHeaderPropagation compiles regex patterns at startup. BuildRequestHeaderForSubgraph and ApplyResponseHeaderRules apply pattern rules in config order between global and exact per-subgraph rules. Helper subgraphPatternMatches evaluates pattern applicability with negation support. getAllRules and SubgraphRules include pattern-derived rules.
Request and response header pattern tests
router/core/header_rule_engine_buildheader_test.go, router/core/header_rule_engine_test.go
Tests verify pattern-based rules apply to matching names, respect negation and precedence, handle invalid regexes, and produce correct propagated headers. SubgraphRules helper correctly includes pattern rules alongside global and exact rules.
User documentation
docs-website/router/proxy-capabilities/subgraph-*-header-operations.mdx
Configuration examples show pattern-based rules propagating headers to subgraphs matching regex selectors. Explanatory text describes rule evaluation order and supported pattern selectors.

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main feature being added: regex-based selectors for subgraph header rules via a new subgraph_patterns configuration.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
docs-website/router/proxy-capabilities/subgraph-request-header-operations.mdx (1)

70-70: ⚡ Quick win

Split this into two short reference sentences.

Line 70 is dense for reference-style docs. Split the example rationale into two short statements.

As per coding guidelines, "Prefer short, declarative sentences. If a sentence has more than one comma-separated clause, consider splitting it."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@docs-website/router/proxy-capabilities/subgraph-request-header-operations.mdx`
at line 70, The sentence explaining subgraph_patterns and matching is too dense;
split it into two short declarative sentences: first state that the
subgraph_patterns section applies rules to every subgraph whose name matches the
regular expression in matching, and then add a second sentence giving the
example rationale (e.g., this is useful for targeting a base subgraph and its
feature subgraphs such as PR-preview deployments named products-feature-pr-123)
so the explanation is clear and concise while keeping the reference focused on
subgraph_patterns and matching.
docs-website/router/proxy-capabilities/subgraph-response-header-operations.mdx (1)

82-82: ⚡ Quick win

Break precedence details into a short list.

This line packs multiple distinct facts into one sentence. In reference docs, a short sentence plus a 3-item order list is easier to scan.

As per coding guidelines, "Use structured lists when presenting multiple distinct items. Do not pack them into a single paragraph."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@docs-website/router/proxy-capabilities/subgraph-response-header-operations.mdx`
at line 82, Replace the dense sentence describing precedence in the
subgraph_patterns paragraph with a short leading sentence and a three-item
ordered list; explicitly state that patterns are evaluated: 1) after the all
rules, 2) before exact subgraphs rules, and 3) that exact subgraphs can still
override a matching pattern (referencing the terms subgraph_patterns, matching,
all, and subgraphs to locate the text).
router/core/header_rule_engine.go (1)

996-1000: ⚖️ Poor tradeoff

Consider recompiling regexes on the request path.

SubgraphRules recompiles pattern regexes on every call (lines 996-1000), while the main engine pre-compiles them once at startup in NewHeaderPropagation (lines 190-210). If FetchURLRules (which calls this function) is invoked frequently during request processing, repeated regex compilation could add latency proportional to the number of patterns.

The PR objectives note this as an open question: "sharing compiled regexes between helpers." Since SubgraphRules is a package-level function, it cannot access the pre-compiled HeaderPropagation.subgraphPatternRegex slice. Alternatives include making it a method, passing regexes as parameters, or introducing a package-level cache.

However, if measurements confirm the current approach adds negligible latency (as the PR objectives suggest: "<10 µs in examples"), this tradeoff may be acceptable for API simplicity.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@router/core/header_rule_engine.go` around lines 996 - 1000, SubgraphRules
currently recompiles patterns on each call; change it to accept and use
precompiled regexes instead (e.g., pass a []*regexp.Regexp or a map of
pattern->*regexp.Regexp) so callers like FetchURLRules can forward
HeaderPropagation.subgraphPatternRegex produced in NewHeaderPropagation;
alternatively make SubgraphRules a method on HeaderPropagation that uses the
existing subgraphPatternRegex field; update all call sites to supply the
precompiled slice and remove the regexp.Compile(...) logic and associated
continue branch inside SubgraphRules.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@docs-website/router/proxy-capabilities/subgraph-request-header-operations.mdx`:
- Around line 86-89: The list items for the selectors (`matching`,
`negate_match`, `request`, `response`) use em dashes; update each bullet in
subgraph-request-header-operations.mdx to remove the em dash and follow the MDX
docs style (replace the em dash with a period or rephrase into a short
sentence), e.g. "`matching` — A Go regular expression..." → "`matching`. A Go
regular expression..." ensuring punctuation and capitalization remain consistent
across all four entries.

---

Nitpick comments:
In
`@docs-website/router/proxy-capabilities/subgraph-request-header-operations.mdx`:
- Line 70: The sentence explaining subgraph_patterns and matching is too dense;
split it into two short declarative sentences: first state that the
subgraph_patterns section applies rules to every subgraph whose name matches the
regular expression in matching, and then add a second sentence giving the
example rationale (e.g., this is useful for targeting a base subgraph and its
feature subgraphs such as PR-preview deployments named products-feature-pr-123)
so the explanation is clear and concise while keeping the reference focused on
subgraph_patterns and matching.

In
`@docs-website/router/proxy-capabilities/subgraph-response-header-operations.mdx`:
- Line 82: Replace the dense sentence describing precedence in the
subgraph_patterns paragraph with a short leading sentence and a three-item
ordered list; explicitly state that patterns are evaluated: 1) after the all
rules, 2) before exact subgraphs rules, and 3) that exact subgraphs can still
override a matching pattern (referencing the terms subgraph_patterns, matching,
all, and subgraphs to locate the text).

In `@router/core/header_rule_engine.go`:
- Around line 996-1000: SubgraphRules currently recompiles patterns on each
call; change it to accept and use precompiled regexes instead (e.g., pass a
[]*regexp.Regexp or a map of pattern->*regexp.Regexp) so callers like
FetchURLRules can forward HeaderPropagation.subgraphPatternRegex produced in
NewHeaderPropagation; alternatively make SubgraphRules a method on
HeaderPropagation that uses the existing subgraphPatternRegex field; update all
call sites to supply the precompiled slice and remove the regexp.Compile(...)
logic and associated continue branch inside SubgraphRules.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0536864b-3573-4100-bad3-413e19a623c4

📥 Commits

Reviewing files that changed from the base of the PR and between dfd3089 and a630df6.

📒 Files selected for processing (9)
  • docs-website/router/proxy-capabilities/subgraph-request-header-operations.mdx
  • docs-website/router/proxy-capabilities/subgraph-response-header-operations.mdx
  • router/core/header_rule_engine.go
  • router/core/header_rule_engine_buildheader_test.go
  • router/core/header_rule_engine_test.go
  • router/pkg/config/config.go
  • router/pkg/config/config.schema.json
  • router/pkg/config/testdata/config_defaults.json
  • router/pkg/config/testdata/config_full.json

Comment on lines +86 to +89
* `matching` — A Go regular expression evaluated against the subgraph name. Required.
* `negate_match` — If `true`, the regex result is inverted. Useful for "all subgraphs except X" rules.
* `request` — Request rules to apply when the pattern matches.
* `response` — Response rules to apply when the pattern matches.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Replace em dashes in selector bullets.

The bullets use em dashes, which conflicts with the docs style rule for MDX files.

Suggested edit
-* `matching` — A Go regular expression evaluated against the subgraph name. Required.
-* `negate_match` — If `true`, the regex result is inverted. Useful for "all subgraphs except X" rules.
-* `request` — Request rules to apply when the pattern matches.
-* `response` — Response rules to apply when the pattern matches.
+* `matching`: A Go regular expression evaluated against the subgraph name. Required.
+* `negate_match`: If `true`, the regex result is inverted. Useful for "all subgraphs except X" rules.
+* `request`: Request rules to apply when the pattern matches.
+* `response`: Response rules to apply when the pattern matches.

As per coding guidelines, "Avoid em dashes. Use periods or restructure the sentence instead."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
* `matching` A Go regular expression evaluated against the subgraph name. Required.
* `negate_match` If `true`, the regex result is inverted. Useful for "all subgraphs except X" rules.
* `request` Request rules to apply when the pattern matches.
* `response` Response rules to apply when the pattern matches.
* `matching`: A Go regular expression evaluated against the subgraph name. Required.
* `negate_match`: If `true`, the regex result is inverted. Useful for "all subgraphs except X" rules.
* `request`: Request rules to apply when the pattern matches.
* `response`: Response rules to apply when the pattern matches.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@docs-website/router/proxy-capabilities/subgraph-request-header-operations.mdx`
around lines 86 - 89, The list items for the selectors (`matching`,
`negate_match`, `request`, `response`) use em dashes; update each bullet in
subgraph-request-header-operations.mdx to remove the em dash and follow the MDX
docs style (replace the em dash with a period or rephrase into a short
sentence), e.g. "`matching` — A Go regular expression..." → "`matching`. A Go
regular expression..." ensuring punctuation and capitalization remain consistent
across all four entries.

@mwisner mwisner marked this pull request as ready for review May 21, 2026 10:45
@mwisner mwisner requested review from a team as code owners May 21, 2026 10:45
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant